import java.util.ArrayList;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class TicketingDS implements TicketingSystem {
		
	// the database system
    private int routenum, coachnum, seatnum, stationnum, threadnum;
    // this is the map of the database's struct
    // [ route
    //   [ seats
    //     [
    //       station
    //     ]
    //   ]
    // ]
    // true means available while false means occupied
    // NOTE : if we use Long to present station , stationnum should no more than 128
    // NOTE : the route num , departure and arrival begin from 1 , but database store them from 0
    // NOTE : the coach num and seat num must begin from 1
    private ArrayList<ArrayList<Long>> database;

    // the lock system
    private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();

    // the tid controller
    private AtomicInteger tid_gen = new AtomicInteger(0);


    public TicketingDS(int routenum, int coachnum, int seatnum, int stationnum, int threadnum) {

        this.routenum = routenum;
        this.coachnum = coachnum;
        this.seatnum = seatnum;
        this.stationnum = stationnum;
        this.threadnum = threadnum;

        database = new ArrayList<>(routenum);

        for (int i=0; i<routenum ; i++) {
            ArrayList<Long> temp_route = new ArrayList<>();
            for(int j=0; j<(coachnum * seatnum); j++) {
                long temp_seat = 0;
                for(int k=0; k<stationnum; k++) {
                    temp_seat = (temp_seat | (1 << k));
                }
                temp_route.add(temp_seat);
            }
            database.add(temp_route);
        }
    }
    private long getMask(int departure, int arrival) {
        long mask = 0;
        for(int i=0;i<this.stationnum;i++) {
            if(i >= (departure - 1) && i <= (arrival - 1))
                mask = (mask | (1 << i));
        }
        return mask;
    }

    public Ticket buyTicket(String passenger, int route, int departure, int arrival) {

        long mask = getMask(departure, arrival);
        Ticket result = null;

        lock.writeLock().lock();

        ArrayList<Long> this_route = database.get(route - 1);
        // search all the coach

        for(int i=0;i<this_route.size();i++) {
            long seat = this_route.get(i);
            if((mask & seat) == mask) {
                // we get an empty seat
                result = new Ticket();
                result.tid = this.tid_gen.getAndIncrement();
                result.passenger = passenger;
                result.route = route;
                result.coach = (i / this.seatnum) + 1;
                result.seat = (i % this.seatnum) + 1;
                result.departure = departure;
                result.arrival = arrival;

                // occupy this seat
                this_route.set(i, (seat & ~mask));

                lock.writeLock().unlock();
                return result;
            }
        }
        // no seats available

        lock.writeLock().unlock();
        return result;
    }

    public int inquiry(int route, int departure, int arrival) {

        long mask = getMask(departure, arrival);

        lock.readLock().lock();

        ArrayList<Long> this_route = database.get(route - 1);
        int result = 0;

        for(int i=0;i<this_route.size();i++) {
            if((mask & this_route.get(i)) == mask) {
                result++;
            }
        }

        lock.readLock().unlock();

        return result;
    }

    public boolean refundTicket(Ticket ticket) {

        long mask = getMask(ticket.departure, ticket.arrival);
        int ticket_coach = ticket.coach;
        int ticket_seat = ticket.seat;
        int ticket_route = ticket.route;
        // this is the index in array
        int index = (ticket_coach - 1) * this.seatnum + (ticket_seat - 1);

        if(ticket == null) return false;

        lock.writeLock().lock();

        ArrayList<Long> this_route = database.get(ticket_route - 1);

        // check the validation of the ticket
        long seat = this_route.get(index);
        if((seat & mask) != 0) {
            lock.writeLock().unlock();
            return false;
        }

        // free this seat
        this_route.set(index, (seat | mask));

        lock.writeLock().unlock();
        return true;

    }

}
